﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Software41.Tracking.Tracing;

namespace Software41.Tracking.BAM
{
    [Serializable]
    public class ExceptionDetail
    {
        internal const string EXINFO_ACTIVITY = "ExceptionDetail";
        internal const string EXINFO_ACTIVITY_ACTIONSTAKEN = "ActionsTaken";
        internal const string EXINFO_ACTIVITY_FAULTDATA = "FaultData";
        internal const string EXINFO_ACTIVITY_MESSAGE = "Message";
        internal const string EXINFO_ACTIVITY_PROCESS_SOURCE = "ProcessSource";
        internal const string EXINFO_ACTIVITY_SOURCE = "Source";
        internal const string EXINFO_ACTIVITY_STACKTRACE = "StackTrace";
        internal const string EXINFO_ACTIVITY_TYPE = "Type";

        public ActivityStateType State { get; internal set; }
        public string ExceptionDetailId { get; set; }
        public Exception InnerException { get; set; }
        private IEventStream EventStream { get; set; }
        private StringBuilder ActionsTaken { get; set; }

        public ExceptionDetail(Exception ex, string exceptionInfoId, IEventStream eventStream)
        {
            this.InnerException = ex;
            this.ExceptionDetailId = exceptionInfoId;
            this.EventStream = eventStream;
            this.ActionsTaken = new StringBuilder();
        }

        public ExceptionDetail(Exception ex, IEventStream eventStream)
            : this(ex, null, eventStream)
        {
        }


        public ExceptionDetail(Exception ex)
            : this(ex, Guid.NewGuid().ToString(), new Adapters.OrchestrationEventStreamAdapter())
        {
        }


        /// <summary>
        /// This constructor should be called ONLY if you are Updating/Ending 
        /// an already started ExceptionInfo activity
        /// </summary>
        /// <param name="exceptionInfoId"></param>
        /// <param name="eventstream"></param>
        public ExceptionDetail(string exceptionInfoId, IEventStream eventstream)
            : this(null, exceptionInfoId, eventstream)
        {
            this.State = ActivityStateType.Started;
        }

        /// <summary>
        /// This constructor should be called ONLY if you are Updating/Ending 
        /// an already started ExceptionInfo activity
        /// </summary>
        /// <param name="exceptionInfoId"></param>
        public ExceptionDetail(string exceptionInfoId)
            : this(null, exceptionInfoId, new Adapters.OrchestrationEventStreamAdapter())
        {
            this.State = ActivityStateType.Started;
        }

        #region Begin

        public string Begin(ProcessTraceInfo processInfo)
        {
            if (processInfo == null)
                throw new ArgumentNullException("processInfo");

            this.BeginInternal();
            this.AddRelatedActivity(ActivityType.ProcessTraceInfo.ToString(), processInfo.ProcessInfoId);
            return this.ExceptionDetailId;
        }

        public string Begin(StepInfo stepInfo)
        {
            if (stepInfo == null)
                throw new ArgumentNullException("stepInfo");

            this.BeginInternal();
            this.AddRelatedActivity(ActivityType.StepInfo.ToString(), stepInfo.StepInfoId);
            return this.ExceptionDetailId;
        }

        public string Begin(ActionInfo actionInfo)
        {
            if (actionInfo == null)
                throw new ArgumentNullException("actionInfo");

            this.BeginInternal();
            this.AddRelatedActivity(ActivityType.ActionInfo.ToString(), actionInfo.ActionInfoId);
            return this.ExceptionDetailId;
        }

        private void BeginInternal()
        {
            if (string.IsNullOrEmpty(this.ExceptionDetailId))
                this.ExceptionDetailId = Guid.NewGuid().ToString();

            this.EventStream.BeginActivity(EXINFO_ACTIVITY, this.ExceptionDetailId);

            this.UpdateActivity(EXINFO_ACTIVITY_SOURCE, this.InnerException.Source);
            this.UpdateActivity(EXINFO_ACTIVITY_MESSAGE, this.InnerException.Message);
            this.UpdateActivity(EXINFO_ACTIVITY_STACKTRACE, this.InnerException.StackTrace);
            this.UpdateActivity(EXINFO_ACTIVITY_TYPE, this.InnerException.GetType().ToString());

            this.TraceBeginEvent();

            //TODO - Add a related data to save the binarily (not a word!) serialized exception message
            

            this.State = ActivityStateType.Started;
        }
        #endregion

        public void UpdateActionsTaken(string descOfActionsTaken)
        {
            this.UpdateActionsTaken(descOfActionsTaken, false);
        }

        public void UpdateActionsTaken(string descOfActionsTaken, bool overwriteExisting)
        {

            if (overwriteExisting)
                this.ActionsTaken = new StringBuilder();

            this.ActionsTaken.AppendLine(descOfActionsTaken);

        }

        public void End()
        {
            if (this.ActionsTaken.Length > 0)
                this.UpdateActivity(EXINFO_ACTIVITY_ACTIONSTAKEN, this.ActionsTaken.ToString());

            this.EventStream.EndActivity(EXINFO_ACTIVITY, this.ExceptionDetailId);

            this.TraceEndEvent();

            this.State = ActivityStateType.Complete;
        }


        #region Private Helpers
        private void AddRelatedActivity(string relatedActivityName, string releatedActivityInstance)
        {
            this.EventStream.AddRelatedActivity(EXINFO_ACTIVITY, this.ExceptionDetailId,
                                                    relatedActivityName, releatedActivityInstance);
        }


        private void UpdateActivity(string ItemName, object ItemValue)
        {
            this.EventStream.UpdateActivity(EXINFO_ACTIVITY, this.ExceptionDetailId, ItemName, ItemValue);
        }


        private void TraceBeginEvent()
        {
            WorkflowProcessTraceSource.TraceEvent(TraceEventType.Information, ProcessDiagnosticsEventId.ExceptionInfoStart,
                                    string.Format("Begin ExceptionInfo Event.  ExceptionInfoName: [{0}], Message: [{1}]", this.InnerException.GetType().Name, this.InnerException.Message));

        }


        private void TraceEndEvent()
        {
            WorkflowProcessTraceSource.TraceEvent(TraceEventType.Information, ProcessDiagnosticsEventId.StepInfoEnd,
                                    string.Format("End ExceptionInfo Event.  ExceptionInfoName: [{0}], Message: [{1}]", this.InnerException.GetType().Name, this.InnerException.Message));

        }

        #endregion




    }
}
